package top.saplf.library.ui

import android.content.Context
import android.text.Layout
import android.text.StaticLayout
import android.text.TextPaint
import android.util.AttributeSet
import android.util.Log
import android.util.TypedValue
import android.widget.TextView
import java.io.File
import java.io.RandomAccessFile

/**
 * @author saplf
 */
class ReaderTextView : TextView {

    companion object {
        private const val CACHE_LIMIT = 2048
        private const val CACHE_BYTE_LIMIT = 2048 shl 1
        private const val ONE_SCREEN_LIMIT = 800
    }

    var file: File? = null
        set(value) {
            field = value
            mRandomAccess = if (value == null) null else RandomAccessFile(value, "r")
            text = obtainShowContent()
        }
    var fileUrl: String = ""
        set(value) {
            field = value
            val tmp = File(value)
            file = if (tmp.exists()) tmp else null
        }
    var pointerChangeAction: ((start: Long) -> Unit)? = null
    var currentPageSeek: Long = 0
        set(value) {
            field = value
            pointerChangeAction?.invoke(value)
        }
    private var prevPageSeek: Long = 0

    private var mFileCache: String = ""
    private var mStartPointer: Int = 0
    private var mRandomAccess: RandomAccessFile? = null
    private var mEndOfFile: Boolean = false

    private val mTextPaint: TextPaint
            by lazy {
                val declaredField = TextView::class.java.getDeclaredField("mTextPaint")
                declaredField.isAccessible = true
                declaredField.get(this) as TextPaint
            }

    constructor(context: Context) : this(context, null)

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)

    fun accSize() {
        setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize + 1f)
    }

    fun decSize() {
        text = obtainShowContent()
        setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize - 1f)
    }

    fun nextPage(): Boolean {
        val currentText = obtainShowContent()
        if (currentText.isEmpty()) return false
        val showText = currentText.subSequence(0, getCharNum()).toString()
        val endPointer = mStartPointer + showText.length
        mStartPointer = endPointer
        currentPageSeek += showText.toByteArray().size
        text = obtainShowContent()
        return true
    }

    fun prevPage(): Boolean {
        var pointer = mStartPointer - 1
        while (true) {
            if (pointer < 0) {
                pointer = mStartPointer
                loadPrevContent()
                pointer = mStartPointer - pointer
            }
            if (fitWindow(pointer)) {
                break
            }
            pointer--
        }
        mStartPointer = pointer
        text = obtainShowContent()
        currentPageSeek = prevPageSeek + mFileCache.substring(0, pointer).toByteArray().size
        return true
    }

    fun previewText(): String {
        val text = obtainShowContent()
        val builder = StringBuilder()
        text.forEach {
            builder.append(it)
            if (builder.length > 10 || it == '\n') {
                return builder.toString()
            }
        }
        return builder.toString()
    }

    fun jumpTo(seek: Long) {
        currentPageSeek = seek
        mStartPointer = 0
        mFileCache = ""
        text = obtainShowContent()
    }

    private fun fitWindow(startPointer: Int): Boolean {
        val text = mFileCache.subSequence(startPointer, Math.min(startPointer + ONE_SCREEN_LIMIT, mFileCache.length))
        val layout = StaticLayout(
                text,
                mTextPaint,
                measuredWidth,
                Layout.Alignment.ALIGN_NORMAL,
                lineSpacingMultiplier,
                lineSpacingExtra, false)
        return getCharNum(layout) + startPointer < mStartPointer
    }

    private fun obtainShowContent(): String {
        var tmpEnd = mStartPointer + ONE_SCREEN_LIMIT
        var cacheLength = mFileCache.length
        if (!mEndOfFile && (mStartPointer > cacheLength || tmpEnd > cacheLength)) {
            loadPostContent()
            tmpEnd = ONE_SCREEN_LIMIT
            cacheLength = mFileCache.length
            if (cacheLength == 0) {
                return ""
            }
            if (tmpEnd > cacheLength) {
                tmpEnd = cacheLength
            }
        }
        return mFileCache.substring(mStartPointer..tmpEnd)
    }

    private fun loadPrevContent() {
        mRandomAccess?.run {
            val content = obtainShowContent()
            var currentLength = currentPageSeek + if (content.isEmpty()) 0 else content.subSequence(0, getCharNum()).toString().toByteArray().size
            currentLength -= CACHE_BYTE_LIMIT
            val separator = '\n'.toInt()
            while (true) {
                if (currentLength <= 0) {
                    loadSpecifyContent(0, true)
                    return
                }
                seek(currentLength)
                if (read() == separator) {
                    break
                }
                currentLength -= 2
            }
            loadSpecifyContent(filePointer, true)
        }
    }

    private fun loadPostContent() {
        loadSpecifyContent(currentPageSeek)
        mStartPointer = 0
    }

    private fun loadSpecifyContent(seek: Long, postLimit: Boolean = false) {
        mRandomAccess?.run {
            if (mEndOfFile) return
            prevPageSeek = seek
            seek(seek)
            val builder = StringBuilder()
            val limitSeek = if (postLimit) with(obtainShowContent()) {
                // 当前显示页面结尾的指针
                if (isEmpty()) {
                    currentPageSeek
                } else {
                    currentPageSeek + substring(0, getCharNum()).toByteArray().size
                }
            } else 0
            while (true) {
                val line = readLine()
                if (line == null) {
                    builder.append('\n')
                    mEndOfFile = true
                    break
                }
                val result = String(line.toByteArray(Charsets.ISO_8859_1), Charsets.UTF_8)
                builder.append(result).append('\n')
                if (filePointer > limitSeek && builder.length > CACHE_LIMIT) break
            }
            builder.deleteCharAt(builder.length - 1)
            mFileCache = builder.toString()
            mStartPointer += String(mFileCache.toByteArray().sliceArray(0..(currentPageSeek - seek).toInt()), Charsets.UTF_8).length
        }
    }

    /**
     * 获取当前页总字数
     */
    fun getCharNum(layout: Layout = this@ReaderTextView.layout): Int {
        return layout.getLineEnd(getLineNum(layout))
    }

    /**
     * 获取当前页总行数
     */
    fun getLineNum(layout: Layout = this@ReaderTextView.layout): Int {
        val topOfLastLine = height - paddingTop - paddingBottom - lineHeight
        return layout.getLineForVertical(topOfLastLine)
    }

    fun close() {
        mRandomAccess?.close()
        pointerChangeAction = null
    }
}

